package cn.jbolt.core.handler.base;

import cn.hutool.core.util.StrUtil;
import cn.jbolt.core.api.JBoltApiJwtManger;
import cn.jbolt.core.api.JBoltApiKit;
import cn.jbolt.core.api.JBoltJwtParseRet;
import cn.jbolt.core.base.JBoltRequestType;
import cn.jbolt.core.base.config.JBoltConfig;
import cn.jbolt.core.bean.JBoltOfModuleInfo;
import cn.jbolt.core.cache.JBoltApplicationCache;
import cn.jbolt.core.cache.JBoltOnlineUserCache;
import cn.jbolt.core.consts.JBoltConst;
import cn.jbolt.core.db.util.JBoltTxKit;
import cn.jbolt.core.kit.*;
import cn.jbolt.core.model.Application;
import cn.jbolt.core.model.OnlineUser;
import cn.jbolt.core.safe.XssUtil;
import cn.jbolt.core.service.JBoltUserService;
import cn.jbolt.core.util.JBoltArrayUtil;
import cn.jbolt.core.util.JBoltCookieUtil;
import com.jfinal.aop.Aop;
import com.jfinal.handler.Handler;
import com.jfinal.kit.HandlerKit;
import com.jfinal.kit.HttpKit;
import com.jfinal.kit.StrKit;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;

/**
 * JBolt开发平台全局handler
 *
 * @ClassName: JBoltBaseHandler
 * @author: JFinal学院-小木 QQ：909854136
 * @date: 2019年12月10日
 */
public class JBoltBaseHandler extends Handler {
	private JBoltSaasTenantSnParser _tenantSnParser = new JBoltSaasTenantSnDefaultParser();
	private String[] unlimiteds;
	/**
	 * 设置租户Sn解析器
	 * @param tenantSnParser
	 */
	public JBoltBaseHandler setTenantSnParser(JBoltSaasTenantSnParser tenantSnParser) {
		this._tenantSnParser = tenantSnParser;
		return this;
	}

	@Override
	public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
		request.setAttribute(JBoltConst.RQKEY_NEED_ALWAYS_HTTPS, JBoltConfig.NEED_ALWAYS_HTTPS);
		//处理scheme
		processScheme(request);
		String basePath = processBasePath(request);
		request.setAttribute(JBoltConst.RQKEY_BASEPATH, basePath);
		if (target.endsWith(JBoltConst.DOT_HTML) && checkNotInUnlimited(target)) {
			HandlerKit.renderError404(request, response, isHandled);
		} else {
			processJBoltRequest(target, request);
			//如果是saas模式就处理租户解析
			if(JBoltConfig.SAAS_ENABLE) {
				JBoltSaasTenantKit.me.clear();
				String tenantSN = _tenantSnParser.parse(request);
				boolean isNull = StrKit.isBlank(tenantSN);
				//如果解析错误 没解析出来 404处理
				if(isNull && _tenantSnParser.isFail()) {
					JBoltHandlerKit.renderError404(request, response, isHandled);
					return;
				}
				if(!isNull && !_tenantSnParser.isFail() && !JBoltConst.WWW.equalsIgnoreCase(tenantSN)) {
					JBoltSaasTenantKit.me.self(tenantSN);
					if(!JBoltSaasTenantKit.me.isAccessible()) {
						String errorMsg = JBoltSaasTenantKit.me.getErrorMsg("当前租户状态未启用，无法访问,请联系平台处理");
						JBoltSaasTenantKit.me.clear();
						JBoltHandlerKit.renderFail(errorMsg,request, response,isHandled);
						return;
					}
				}
			}
			//设置默认模块 后面有需要的会set覆盖
			JBoltCurrentOfModuleKit.setJboltOfModuleInfo(JBoltOfModuleInfo.PLATFORM_INNER_ADMIN_INFO);
			//处理当前登录用户数据
			boolean isApiReq = Objects.equals(request.getAttribute(JBoltConst.RQKEY_JB_RQTYPE), JBoltRequestType.JBOLTAPI);
			if(isApiReq){
				JBoltUserKit.setUserId(JBoltApiKit.getApiUserIdToLong());
			}
			try {
				next.handle(target, request, response, isHandled);
			} finally {
				JBoltUserKit.clear();
				JBoltHttpSchemeKit.remove();
				if(JBoltConfig.SAAS_ENABLE) {
					JBoltSaasTenantKit.me.clear();
				}
				JBoltTxKit.clear();
				if(isApiReq){
					//清空 接口访问信息
					JBoltApiKit.clear();
				}
				JBoltCurrentOfModuleKit.clear();
			}
		}

	}

	/**
	 * 处理请求scheme
	 * @param request
	 * @return
	 */
	private String processScheme(HttpServletRequest request) {
		String scheme=request.getScheme();
		if (JBoltConfig.NEED_ALWAYS_HTTPS) {
			scheme=JBoltConst.HTTPS;
		} else {
			boolean isHttps=HttpKit.isHttps(request);
			if(isHttps) {
				scheme=JBoltConst.HTTPS;
			}
		}
		JBoltHttpSchemeKit.set(scheme);
		return scheme;
	}


	/**
	 * 处理basePath 默认需要最后的斜杠
	 *
	 * @param request
	 * @return
	 */
	public static String processBasePath(HttpServletRequest request) {
		return processBasePath(request, true);
	}

	/**
	 * 处理basePath
	 *
	 * @param request
	 * @param needEndSlash 是否需要最后的斜杠
	 * @return
	 */
	public static String processBasePath(HttpServletRequest request, boolean needEndSlash) {
		return processBasePathStrBuilder(request, needEndSlash).toString();
	}

	/**
	 * 处理basePath 返回StringBUiler
	 *
	 * @param request
	 * @param needEndSlash 是否需要最后的斜杠
	 * @return
	 */
	public static StringBuilder processBasePathStrBuilder(HttpServletRequest request, boolean needEndSlash) {
		StringBuilder basePath = new StringBuilder();
		int port = request.getServerPort();
		basePath.append(JBoltHttpSchemeKit.get()).append(JBoltConst.MHSP).append(request.getServerName());
		if (port != 80) {
			basePath.append(JBoltConst.MH).append(port);
		}
		String contextPath = request.getContextPath();
		request.setAttribute(JBoltConst.RQKEY_CTX, contextPath);
		if (StrKit.isBlank(contextPath)) {
			contextPath = JBoltConst.SLASH_STR;
		} else if (!contextPath.endsWith(JBoltConst.SLASH_STR)) {
			contextPath = contextPath + JBoltConst.SLASH_STR;
		}
		if (needEndSlash) {
			basePath.append(contextPath);
		}
		return basePath;
	}

	/**
	 * 处理pmkey
	 * @param target
	 * @param request
	 */
	private void processRequestPmkey(String target,HttpServletRequest request){
		request.setAttribute(JBoltConst.RQKEY_PMKEY,
				(target.length() >= 2 && target.charAt(0) == JBoltConst.SLASH && target.charAt(1) != JBoltConst.SLASH)
						? target.substring(1)
						: target);
	}
	/**
	 * 处理请求类型
	 *
	 * @param target
	 * @param request
	 */
	private void processJBoltRequest(String target, HttpServletRequest request) {
 		boolean isGet = request.getMethod().equalsIgnoreCase("get");
		if(isGet && JBoltConfig.PROCESS_GET_REQUEST_TARGET_XSS_ENABLE) {
			target = XssUtil.processXSS(target);
		}
		if (isStaticResource(target)) {
			return;
		}

//		System.out.println("请求地址:"+request.getRequestURL());
		request.setAttribute(JBoltConst.JBOLT_BASE_UPLOAD_PATH_PRE, JBoltConfig.BASE_UPLOAD_PATH_PRE);
		request.setAttribute(JBoltConst.JBOLT_GLOBAL_UPLOAD_TO, JBoltConfig.JBOLT_GLOBAL_UPLOAD_TO);
		// 判断是不是API SDK调用
		if (JBoltConst.TRUE.equalsIgnoreCase(request.getHeader(JBoltConst.RQ_HEAD_KEY_JBOLTAPI))) {
			request.setAttribute(JBoltConst.RQKEY_JB_RQTYPE, JBoltRequestType.JBOLTAPI);
			return;
		}

		// 判断是不是pjax
		if (JBoltConst.TRUE.equalsIgnoreCase(request.getHeader(JBoltConst.RQ_HEAD_KEY_XPJAX))) {
			request.setAttribute(JBoltConst.RQKEY_JB_RQTYPE, JBoltRequestType.PJAX);
			if(isGet){
				processRequestPmkey(target,request);
			}
			return;
		}

		// 判断是不是ajaxportal
		if (JBoltConst.TRUE.equalsIgnoreCase(request.getHeader(JBoltConst.RQ_HEAD_KEY_AJAXPORTAL))) {
			request.setAttribute(JBoltConst.RQKEY_JB_RQTYPE, JBoltRequestType.AJAXPORTAL);
			if(isGet){
				processRequestPmkey(target,request);
			}
			return;
		}

		// 判断是不是 ajax
		String xrequestedwith = request.getHeader(JBoltConst.RQ_HEAD_KEY_XREQUESTEDWITH);
		if ((xrequestedwith != null && JBoltConst.XMLHTTPREQUEST.equalsIgnoreCase(xrequestedwith))) {
			request.setAttribute(JBoltConst.RQKEY_JB_RQTYPE, JBoltRequestType.AJAX);
			return;
		}

		//判断是不是ajaxdownload
		if(JBoltConst.TRUE.equalsIgnoreCase(request.getHeader(JBoltConst.RQ_HEAD_KEY_AJAX_DOWNLOAD))) {
			request.setAttribute(JBoltConst.RQKEY_JB_RQTYPE,JBoltRequestType.AJAX);
			return;
		}

		// 判断是不是 contentType json
		String contentType = request.getContentType();
		if (StrKit.notBlank(contentType) && contentType.startsWith(JBoltConst.RQ_CONTENT_TYPE_JSON)) {
			request.setAttribute(JBoltConst.RQKEY_JB_RQTYPE, JBoltRequestType.JSON);
			return;
		}

		// 判断是不是 accept json
		String accept = request.getHeader(JBoltConst.RQ_HEAD_KEY_ACCEPT);
		if (StrKit.notBlank(accept) && accept.contains(JBoltConst.RQ_CONTENT_TYPE_JSON)) {
			request.setAttribute(JBoltConst.RQKEY_JB_RQTYPE, JBoltRequestType.JSON);
			return;
		}

		String rqParam = request.getParameter(JBoltConst.RQKEY_JB_RQTYPE);
		// 判断URL参数中带_jb_rqtype_=dialog
		if (JBoltConst.RQ_TYPE_DIALOG.equalsIgnoreCase(rqParam)) {
			request.setAttribute(JBoltConst.RQKEY_JB_RQTYPE, JBoltRequestType.DIALOG);
			if(isGet){
				processRequestPmkey(target,request);
			}
			return;
		}
		// 判断URL参数中带_jb_rqtype_=iframe
		if (JBoltConst.RQ_TYPE_IFRAME.equalsIgnoreCase(rqParam)) {
			request.setAttribute(JBoltConst.RQKEY_JB_RQTYPE, JBoltRequestType.IFRAME);
			if(isGet){
				processRequestPmkey(target,request);
			}
			return;
		}

		// 判断请求地址上带着_jb_rqtype_dialog 例如localhost/demo/table/1-2-_jb_rqtype_dialog
		String url = request.getRequestURI();
		if (StrKit.notBlank(url)) {
			if (url.endsWith(JBoltConst.RQ_TYPE_DIALOG_STR)) {
				request.setAttribute(JBoltConst.RQKEY_JB_RQTYPE, JBoltRequestType.DIALOG);
				if(isGet){
					processRequestPmkey(target,request);
				}
				return;
			}
			if (url.endsWith(JBoltConst.RQ_TYPE_IFRAME_STR)) {
				request.setAttribute(JBoltConst.RQKEY_JB_RQTYPE, JBoltRequestType.IFRAME);
				if(isGet){
					processRequestPmkey(target,request);
				}
				return;
			}
		}
		if(isGet){
			processRequestPmkey(target,request);
		}
		request.setAttribute(JBoltConst.RQKEY_JB_RQTYPE, JBoltRequestType.NORMAL);
	}

	/**
	 * 判断js css 图片等静态资源地址
	 *
	 * @param target
	 * @return
	 */
	private boolean isStaticResource(String target) {
		int index = target.lastIndexOf(JBoltConst.DOT);
		if (index == -1) {
			// 没有就直接返回false 不是静态资源
			return false;
		}
		String suffixes = target.substring(index);
		return StrUtil.endWithAnyIgnoreCase(suffixes, JBoltConst.RESOURCE_SUFFIXES);
	}

	/**
	 * 检测是否属于忽略检测
	 *
	 * @param target
	 * @return
	 */
	private boolean checkNotInUnlimited(String target) {
		if (unlimiteds == null || unlimiteds.length == 0) {
			return true;
		}
		boolean in = false;
		for (String l : unlimiteds) {
			if (target.equals(l) || target.contains(l)) {
				in = true;
				break;
			}
		}
		return !in;
	}

	/**
	 * 设置不检测的路径
	 *
	 * @param unlimiteds
	 * @return
	 */
	public JBoltBaseHandler unlimited(String... unlimiteds) {
		if (JBoltArrayUtil.isEmpty(this.unlimiteds)) {
			this.unlimiteds = unlimiteds;
		} else {
			this.unlimiteds = JBoltArrayUtil.merge(this.unlimiteds, unlimiteds);
		}
		return this;
	}

}
